import re
import math
import time
import struct
import weakref
import sys

from enum import IntEnum, Flag, auto

from src import findPinClassByType
from src.Core.version import Version
from src import GET_PACKAGES

# 计算系统中整数的最大值
maxint = 2 ** (struct.Struct("i").size * 8 - 1) - 1

# 定义浮点数的最小值
FLOAT_RANGE_MIN = 0.1 + (-maxint - 1.0)
# 定义浮点数的最大值
FLOAT_RANGE_MAX = maxint + 0.1
# 定义整数的最小值
INT_RANGE_MIN = -maxint + 0
# 定义整数的最大值
INT_RANGE_MAX = maxint + 0

# 定义默认的输入执行引脚名称
DEFAULT_IN_EXEC_NAME = "inExec"
# 定义默认的输出执行引脚名称
DEFAULT_OUT_EXEC_NAME = "outExec"
# 定义默认的小部件变体名称
DEFAULT_WIDGET_VARIANT = "DefaultWidget"
# 定义引用标志
REF = "Reference"

# 声明全局变量 GlobalVariables
global GlobalVariables
# 初始化全局变量为一个空字典，用于存储全局数据
GlobalVariables = {}


def fetchPackageNames(graphJson):
    """
    解析序列化的图数据并返回它所使用的所有包名。

    :param graphJson: 序列化的图数据
    :type graphJson: dict
    :rtype: list(str)
    """
    # 使用集合存储包名，确保唯一性
    packages = set()

    def worker(graphData):
        """
        递归遍历图数据，提取包名。

        :param graphData: 图数据
        :type graphData: dict
        """
        # 遍历图中的所有节点
        for node in graphData["nodes"]:
            # 将节点所属的包名添加到集合中
            packages.add(node["package"])

            # 遍历节点的所有输入引脚
            for inpJson in node["inputs"]:
                # 将输入引脚所属的包名添加到集合中
                packages.add(inpJson["package"])

            # 遍历节点的所有输出引脚
            for outJson in node["inputs"]:
                # 将输出引脚所属的包名添加到集合中
                packages.add(outJson["package"])

            # 如果节点包含子图数据，递归调用 worker 函数
            if "graphData" in node:
                worker(node["graphData"])

    # 调用 worker 函数开始处理图数据
    worker(graphJson)
    return list(packages)


def validateGraphDataPackages(graphData, missedPackages=None):
    """
    检查序列化数据中使用的包是否可访问。
    缺失的包名将被添加到输出集合中。

    :param graphData: 序列化的图数据
    :type graphData: dict
    :param missedPackages: 缺失的包名集合
    :type missedPackages: set
    :rtype: bool
    """
    # 如果未提供缺失包名集合，则初始化一个空集合
    if missedPackages is None:
        missedPackages = set()
    # 获取当前所有可用的包名
    existingPackages = GET_PACKAGES().keys()
    # 提取图数据中使用的所有包名
    graphPackages = fetchPackageNames(graphData)
    # 遍历图数据中使用的包名
    for pkg in graphPackages:
        # 如果包名不在可用包名列表中，则将其添加到缺失包名集合中
        if pkg not in existingPackages:
            missedPackages.add(pkg)
    # 如果缺失包名集合为空，则说明所有包都可访问
    return len(missedPackages) == 0


def lerp(start, end, alpha):
    """
    执行线性插值。

    >>> start + alpha * (end - start)

    :param start: 插值的起始值
    :param end: 插值的结束值
    :param alpha: 插值的比例，范围从 0.0 到 1.0
    :returns: 线性插值的结果
    """
    return start + alpha * (end - start)


def GetRangePct(MinValue, MaxValue, Value):
    """
    计算输入值在最小值和最大值之间的百分比。

    :param MinValue: 最小值
    :param MaxValue: 最大值
    :param Value: 输入值
    :returns: 输入值在最小值和最大值之间的百分比，范围从 0.0 到 1.0
    """
    return (Value - MinValue) / (MaxValue - MinValue)


def mapRangeClamped(Value, InRangeA, InRangeB, OutRangeA, OutRangeB):
    """
    将输入值从一个范围映射到另一个范围，并将结果限制在输出范围内。
    （例如，将 0.5 从 0->1 的范围映射到 0->50 的范围，结果为 25）

    :param Value: 输入值
    :param InRangeA: 输入范围的起始值
    :param InRangeB: 输入范围的结束值
    :param OutRangeA: 输出范围的起始值
    :param OutRangeB: 输出范围的结束值
    :return: 映射后的结果，限制在输出范围内
    """
    # 计算输入值在输入范围内的百分比，并将其限制在 0.0 到 1.0 之间
    ClampedPct = clamp(GetRangePct(InRangeA, InRangeB, Value), 0.0, 1.0)
    # 根据百分比在输出范围内进行线性插值
    return lerp(OutRangeA, OutRangeB, ClampedPct)


def mapRangeUnclamped(Value, InRangeA, InRangeB, OutRangeA, OutRangeB):
    """
    将输入值从一个范围映射到另一个范围，不限制结果在输出范围内。
    （例如，将 0.5 从 0->1 的范围映射到 0->50 的范围，结果为 25）

    :param Value: 输入值
    :param InRangeA: 输入范围的起始值
    :param InRangeB: 输入范围的结束值
    :param OutRangeA: 输出范围的起始值
    :param OutRangeB: 输出范围的结束值
    :return: 映射后的结果
    """
    return lerp(OutRangeA, OutRangeB, GetRangePct(InRangeA, InRangeB, Value))


def sign(x):
    """
    返回输入值的符号。如果输入值为负数，返回 -1；如果为正数，返回 1；如果为 0，返回 0。

    >>> x and (1, -1)[x < 0]

    :param x: 输入值
    :return: 输入值的符号
    """
    return x and (1, -1)[x < 0]


def currentProcessorTime():
    """
    获取当前处理器时间。

    :return: 当前处理器时间
    """
    return time.process_time()


def clamp(n, vmin, vmax):
    """
    计算输入值的钳位值，将其限制在最小值和最大值之间。

    :param n: 输入值
    :param vmin: 最小值
    :param vmax: 最大值
    :returns: 钳位后的输入值
    """
    return max(min(n, vmax), vmin)


def roundup(x, to):
    """
    将输入值向上舍入到指定的值。

    >>> roundup(7, 8)
    >>> 8
    >>> roundup(8, 8)
    >>> 8
    >>> roundup(9, 8)
    >>> 16

    :param x: 要舍入的值
    :param to: 舍入的目标值
    :returns: 舍入后的输入值
    :rtype: int
    """
    return int(math.ceil(x / to)) * to


# 获取当前 Python 版本
_currentVersion = Version(sys.version_info.major, sys.version_info.minor, 0)
# 定义 Python 3.2 版本
python32 = Version(3, 2, 0)

if _currentVersion <= python32:
    def clearList(list):
        """
        清空 Python 列表。适用于 Python 3.2 及以下版本。

        :param list: 要清空的列表
        :type list: list
        :returns: 清空后的列表
        :rtype: list
        """
        del list[:]
else:
    def clearList(list):
        """
        清空 Python 列表。适用于 Python 3.2 以上版本。

        :param list: 要清空的列表
        :type list: list
        :returns: 清空后的列表
        :rtype: list
        """
        list.clear()


def findGoodId(ids):
    """
    从可迭代对象中找到最小的唯一整数 ID，从 1 开始。

    :param ids: 已占用的 ID 集合
    :type ids: list|set|tuple
    :returns: 唯一的 ID
    :rtype: int
    """
    # 如果 ID 集合为空，返回 1
    if len(ids) == 0:
        return 1

    # 对 ID 集合进行排序并去重
    ids = sorted(set(ids))
    # 获取最小的 ID
    lastID = min(ids)

    # 如果最小的 ID 大于 1，返回 1
    if lastID > 1:
        return 1

    # 遍历排序后的 ID 集合
    for ID in ids:
        # 计算当前 ID 与上一个 ID 的差值
        diff = ID - lastID
        # 如果差值大于 1，返回上一个 ID 加 1
        if diff > 1:
            return lastID + 1
        lastID = ID
    else:
        # 如果遍历完所有 ID 都没有找到合适的 ID，返回最后一个 ID 加 1
        return ID + 1


def wrapStringToFunctionDef(functionName, scriptString, kwargs=None):
    """
    生成一个函数字符串，该字符串可以被编译和执行。

    示例:
    ::

        wrapStringToFunctionDef('test', 'print(a)', {'a': 5})

    将生成以下函数:
    ::

        def test(a=5):
            print(a)

    :param functionName: 函数名
    :param scriptString: 函数体的脚本字符串
    :param kwargs: 函数的关键字参数，默认为 None
    :return: 生成的函数字符串
    """
    # 初始化关键字参数字符串
    kwargsString = ""
    if kwargs is not None:
        # 遍历关键字参数
        for argname, argValue in kwargs.items():
            if isinstance(argValue, str):
                # 如果参数值是字符串，添加引号
                argValue = "'{}'".format(argValue)
            # 拼接关键字参数字符串
            kwargsString += "{0}={1}, ".format(argname, argValue)
        # 去除最后一个逗号和空格
        kwargsString = kwargsString[:-2]

    # 生成函数定义的头部
    result = "def {0}({1}):\n".format(functionName, kwargsString)

    # 遍历函数体的每一行脚本
    for scriptLine in scriptString.split("\n"):
        # 缩进每一行脚本并添加到结果字符串中
        result += "\t{}".format(scriptLine)
        result += "\n"
    return result


def cycleCheck(src, dst):
    """
    检查连接的节点是否存在循环。

    :param src: 左侧引脚
    :type src: :class:`PyFlow.Core.PinBase.PinBase`
    :param dst: 右侧引脚
    :type dst: :class:`PyFlow.Core.PinBase.PinBase`
    :returns: 如果存在循环，返回 True；否则返回 False
    :rtype: bool
    """
    # 如果源引脚是输入引脚，交换源引脚和目标引脚
    if src.direction == PinDirection.Input:
        src, dst = dst, src
    # 记录起始引脚
    start = src
    # 如果源引脚在目标引脚的影响列表中，说明存在循环
    if src in dst.affects:
        return True
    # 遍历目标引脚的影响列表
    for i in dst.affects:
        # 递归调用 cycleCheck 函数检查是否存在循环
        if cycleCheck(start, i):
            return True
    return False


def arePinsConnected(src, dst):
    """
    检查两个引脚是否连接。

    .. note:: 如果 **src** 引脚是输入引脚，它们将被交换。

    :param src: 左侧引脚
    :type src: :py:class:`PyFlow.Core.PinBase.PinBase`
    :param dst: 右侧引脚
    :type dst: :py:class:`PyFlow.Core.PinBase.PinBase`
    :returns: 如果引脚连接，返回 True；否则返回 False
    :rtype: bool
    """
    # 如果两个引脚的方向相同，返回 False
    if src.direction == dst.direction:
        return False
    # 如果两个引脚属于同一个节点，返回 False
    if src.owningNode() == dst.owningNode():
        return False
    # 如果源引脚是输入引脚，交换源引脚和目标引脚
    if src.direction == PinDirection.Input:
        src, dst = dst, src
    # 如果目标引脚在源引脚的影响列表中，且源引脚在目标引脚的被影响列表中，说明引脚连接
    if dst in src.affects and src in dst.affected_by:
        return True
    return False


def getConnectedPins(pin):
    """
    查找与输入引脚连接的所有引脚。

    :param pin: 要查找连接引脚的引脚
    :type pin: :py:class:`PyFlow.Core.PinBase.PinBase`
    :returns: 连接的引脚集合
    :rtype: set(:py:class:`PyFlow.Core.PinBase.PinBase`)
    """
    # 初始化结果集合
    result = set()
    # 如果引脚是输入引脚
    if pin.direction == PinDirection.Input:
        # 遍历引脚的被影响列表，将所有影响它的引脚添加到结果集合中
        for lhsPin in pin.affected_by:
            result.add(lhsPin)
    # 如果引脚是输出引脚
    if pin.direction == PinDirection.Output:
        # 遍历引脚的影响列表，将所有受它影响的引脚添加到结果集合中
        for rhsPin in pin.affects:
            result.add(rhsPin)
    return result


def pinAffects(lhs, rhs):
    """
    建立引脚之间的依赖关系。

    .. warning:: 内部使用，用户很少需要使用此函数。

    :param lhs: 第一个要连接的引脚
    :type lhs: :py:class:`PyFlow.Core.PinBase.PinBase`
    :param rhs: 第二个要连接的引脚
    :type rhs: :py:class:`PyFlow.Core.PinBase.PinBase`
    """
    # 断言两个引脚不能是同一个引脚
    assert lhs is not rhs, "pin can not affect itself"
    # 将右侧引脚添加到左侧引脚的影响列表中
    lhs.affects.add(rhs)
    # 将左侧引脚添加到右侧引脚的被影响列表中
    rhs.affected_by.add(lhs)


def canConnectPins(src, dst):
    """
    **非常重要的基础函数，用于检查两个引脚之间是否可以连接**

    :param src: 要连接的源引脚
    :type src: :py:class:`PyFlow.Core.PinBase.PinBase`
    :param dst: 要连接的目标引脚
    :type dst: :py:class:`PyFlow.Core.PinBase.PinBase`
    :returns: 如果可以连接，返回 True；否则返回 False
    :rtype: bool
    """
    # 如果源引脚或目标引脚为 None，返回 False
    if src is None or dst is None:
        return False

    # 如果两个引脚的方向相同，返回 False
    if src.direction == dst.direction:
        return False

    # 如果两个引脚已经连接，返回 False
    if arePinsConnected(src, dst):
        return False

    # 如果源引脚是输入引脚，交换源引脚和目标引脚
    if src.direction == PinDirection.Input:
        src, dst = dst, src

    # 如果连接会导致循环，返回 False
    if cycleCheck(src, dst):
        return False

    # 如果两个引脚都是执行引脚，返回 True
    if src.isExec() and dst.isExec():
        return True

    # 如果源引脚不是数组，而目标引脚是数组
    if not src.isArray() and dst.isArray():
        # 如果目标引脚只支持数组
        if dst.optionEnabled(PinOptions.SupportsOnlyArrays):
            # 如果源引脚不能改变结构以匹配目标引脚，返回 False
            if not src.canChangeStructure(dst._currStructure, []):
                return False

        # 如果目标引脚不能改变结构以匹配源引脚，且源引脚也不能改变结构以匹配目标引脚，返回 False
        if not dst.canChangeStructure(src._currStructure, [], selfCheck=False):
            if not src.canChangeStructure(dst._currStructure, [], selfCheck=False):
                return False

    # 如果源引脚不是字典，而目标引脚是字典
    if not src.isDict() and dst.isDict():
        # 如果目标引脚只支持数组
        if dst.optionEnabled(PinOptions.SupportsOnlyArrays):
            # 如果源引脚不能改变结构以匹配目标引脚，且目标引脚也不能改变结构以匹配源引脚，返回 False
            if not (
                src.canChangeStructure(dst._currStructure, [])
                or dst.canChangeStructure(src._currStructure, [], selfCheck=False)
            ):
                return False
        # 如果源引脚不支持字典元素，且目标引脚只支持数组，且目标引脚不能改变结构以匹配源引脚，返回 False
        elif (
            not src.supportDictElement(
                [], src.optionEnabled(PinOptions.DictElementSupported)
            )
            and dst.optionEnabled(PinOptions.SupportsOnlyArrays)
            and not dst.canChangeStructure(src._currStructure, [], selfCheck=False)
        ):
            return False
        else:
            # 获取源引脚的字典元素节点
            DictElementNode = src.getDictElementNode([])
            # 获取目标引脚的字典节点
            dictNode = dst.getDictNode([])
            # 标记字典节点的键是否可用
            nodeFree = False
            if dictNode:
                nodeFree = dictNode.KeyType.checkFree([])
            if DictElementNode:
                # 如果字典元素节点的键不可用，且字典节点的键也不可用，且目标引脚的数据键类型与字典元素节点的键数据类型不匹配，返回 False
                if not DictElementNode.key.checkFree([]) and not nodeFree:
                    if dst._data.keyType != DictElementNode.key.dataType:
                        return False

    # 如果源引脚是数组，而目标引脚不是数组
    if src.isArray() and not dst.isArray():
        # 检查源引脚是否可以改变结构以匹配目标引脚
        srcCanChangeStruct = src.canChangeStructure(dst._currStructure, [])
        # 检查目标引脚是否可以改变结构以匹配源引脚
        dstCanChangeStruct = dst.canChangeStructure(
            src._currStructure, [], selfCheck=False
        )
        # 如果目标引脚不支持数组，且源引脚和目标引脚都不能改变结构以匹配对方，返回 False
        if not dst.optionEnabled(PinOptions.ArraySupported) and not (
            srcCanChangeStruct or dstCanChangeStruct
        ):
            return False

    # 如果源引脚是字典，而目标引脚不是字典
    if src.isDict() and not dst.isDict():
        # 检查源引脚是否可以改变结构以匹配目标引脚
        srcCanChangeStruct = src.canChangeStructure(dst._currStructure, [])
        # 检查目标引脚是否可以改变结构以匹配源引脚
        dstCanChangeStruct = dst.canChangeStructure(
            src._currStructure, [], selfCheck=False
        )
        # 如果目标引脚不支持字典，且源引脚和目标引脚都不能改变结构以匹配对方，返回 False
        if not dst.optionEnabled(PinOptions.DictSupported) and not (
            srcCanChangeStruct or dstCanChangeStruct
        ):
            return False

    # 如果目标引脚已经有连接
    if dst.hasConnections():
        # 如果目标引脚不允许多个连接，且重连策略为禁止连接，返回 False
        if (
            not dst.optionEnabled(PinOptions.AllowMultipleConnections)
            and dst.reconnectionPolicy == PinReconnectionPolicy.ForbidConnection
        ):
            return False

    # 如果源引脚已经有连接
    if src.hasConnections():
        # 如果源引脚不允许多个连接，且重连策略为禁止连接，返回 False
        if (
            not src.optionEnabled(PinOptions.AllowMultipleConnections)
            and src.reconnectionPolicy == PinReconnectionPolicy.ForbidConnection
        ):
            return False

    # 如果源引脚所属节点的图为空，或目标引脚所属节点的图为空，返回 False
    if src.owningNode().graph() is None or dst.owningNode().graph() is None:
        return False

    # 如果源引脚所属节点的图与目标引脚所属节点的图不同，返回 False
    if src.owningNode().graph() is not dst.owningNode().graph():
        return False

    # 如果源引脚是任意类型，而目标引脚是执行引脚
    if src.isAny() and dst.isExec():
        # 如果源引脚的数据类型不在目标引脚支持的数据类型列表中，返回 False
        if src.dataType not in dst.supportedDataTypes():
            return False

    # 如果源引脚是执行引脚，而目标引脚不是执行引脚，返回 False
    if src.isExec() and not dst.isExec():
        return False

    # 如果源引脚不是执行引脚，而目标引脚是执行引脚，返回 False
    if not src.isExec() and dst.isExec():
        return False

    # 如果源引脚和目标引脚都是值引脚
    if src.IsValuePin() and dst.IsValuePin():
        # 如果源引脚的数据类型在目标引脚允许的数据类型列表中，或目标引脚的数据类型在源引脚允许的数据类型列表中
        if src.dataType in dst.allowedDataTypes(
            [], dst._supportedDataTypes
        ) or dst.dataType in src.allowedDataTypes([], src._supportedDataTypes):
            # 检查源引脚和目标引脚的类型改变条件
            a = src.dataType == "AnyPin" and not src.canChangeTypeOnConnection(
                [], src.optionEnabled(PinOptions.ChangeTypeOnConnection), []
            )
            b = dst.canChangeTypeOnConnection(
                [], dst.optionEnabled(PinOptions.ChangeTypeOnConnection), []
            ) and not dst.optionEnabled(PinOptions.AllowAny)
            c = not dst.canChangeTypeOnConnection(
                [], dst.optionEnabled(PinOptions.ChangeTypeOnConnection), []
            ) and not dst.optionEnabled(PinOptions.AllowAny)
            # 如果满足特定条件，返回 False
            if all([a, b or c]):
                return False
            # 如果源引脚不是字典，而目标引脚只支持字典元素，且目标引脚不可用，且目标引脚不能改变结构以匹配源引脚，返回 False
            if (
                not src.isDict()
                and dst.supportOnlyDictElement([], dst.isDict())
                and not (
                    dst.checkFree([], selfCheck=False)
                    and dst.canChangeStructure(src._currStructure, [], selfCheck=False)
                )
            ):
                if not src.supportDictElement(
                    [], src.optionEnabled(PinOptions.DictElementSupported)
                ) and dst.supportOnlyDictElement([], dst.isDict()):
                    return False
            return True
        else:
            # 如果源引脚的数据类型不在目标引脚支持的数据类型列表中，返回 False
            if src.dataType not in dst.supportedDataTypes():
                return False

            # 检查特定条件，如果满足则返回 True
            if all(
                [
                    src.dataType
                    in list(
                        dst.allowedDataTypes(
                            [],
                            dst._defaultSupportedDataTypes,
                            selfCheck=dst.optionEnabled(
                                PinOptions.AllowMultipleConnections
                            ),
                            defaults=True,
                        )
                    )
                    + ["AnyPin"],
                    dst.checkFree(
                        [],
                        selfCheck=dst.optionEnabled(
                            PinOptions.AllowMultipleConnections
                        ),
                    ),
                ]
            ):
                return True
            # 检查特定条件，如果满足则返回 True
            if all(
                [
                    dst.dataType
                    in list(
                        src.allowedDataTypes(
                            [], src._defaultSupportedDataTypes, defaults=True
                        )
                    )
                    + ["AnyPin"],
                    src.checkFree([]),
                ]
            ):
                return True
        return False

    # 如果源引脚所属节点与目标引脚所属节点相同，返回 False
    if src.owningNode == dst.owningNode:
        return False

    return True


def connectPins(src, dst):
    """
    **连接两个引脚**

    引脚连接的规则如下:

    * 如果 :py:class:`PyFlow.Core.Common.PinOptions.AllowMultipleConnections` 标志禁用，输入值引脚只能有一个输出连接
    * 输出值引脚可以有任意数量的连接
    * 输入执行引脚可以有任意数量的连接
    * 输出执行引脚只能有一个连接

    :param src: 左侧引脚
    :type src: :py:class:`PyFlow.Core.PinBase.PinBase`
    :param dst: 右侧引脚
    :type dst: :py:class:`PyFlow.Core.PinBase.PinBase`
    :returns: 如果连接成功，返回 True；否则返回 False
    :rtype: bool
    """
    # 如果源引脚是输入引脚，交换源引脚和目标引脚
    if src.direction == PinDirection.Input:
        src, dst = dst, src

    # 如果两个引脚不能连接，返回 False
    if not canConnectPins(src, dst):
        return False

    # 如果源引脚和目标引脚都是值引脚
    if src.IsValuePin() and dst.IsValuePin():
        # 如果目标引脚已经有连接
        if dst.hasConnections():
            # 如果目标引脚不允许多个连接，断开所有连接
            if not dst.optionEnabled(PinOptions.AllowMultipleConnections):
                dst.disconnectAll()

    # 如果源引脚和目标引脚都是执行引脚
    if src.isExec() and dst.isExec():
        # 如果源引脚已经有连接
        if src.hasConnections():
            # 如果源引脚不允许多个连接，断开所有连接
            if not src.optionEnabled(PinOptions.AllowMultipleConnections):
                src.disconnectAll()

    # 如果源引脚和目标引脚都是执行引脚，将源引脚的执行信号连接到目标引脚的调用方法
    if src.isExec() and dst.isExec():
        src.onExecute.connect(dst.call)

    # 通知目标引脚即将连接源引脚
    dst.aboutToConnect(src)
    # 通知源引脚即将连接目标引脚
    src.aboutToConnect(dst)

    # 建立引脚之间的依赖关系
    pinAffects(src, dst)
    # 标记源引脚为脏数据，需要重新计算
    src.setDirty()

    # 将源引脚的当前数据设置给目标引脚
    dst.setData(src.currentData())

    # 通知目标引脚已连接源引脚
    dst.pinConnected(src)
    # 通知源引脚已连接目标引脚
    src.pinConnected(dst)
    # 调用 push 函数，标记相关引脚为脏数据
    #push(dst)
    return True


def connectPinsByIndexes(lhsNode=None, lhsOutPinIndex=0, rhsNode=None, rhsInPinIndex=0):
    """
    无论引脚名称如何，通过引脚在节点上的位置连接引脚。
    此函数使用引脚在节点上的位置，最顶部的引脚位置索引为 1，下方的引脚索引为 2，依此类推。

    :param lhsNode: 左侧节点
    :type lhsNode: :class:`~PyFlow.Core.NodeBase.NodeBase`
    :param lhsOutPinIndex: 左侧节点上输出引脚的位置索引
    :type lhsOutPinIndex: int
    :param rhsNode: 右侧节点
    :type rhsNode: :class:`~PyFlow.Core.NodeBase.NodeBase`
    :param rhsInPinIndex: 右侧节点上输入引脚的位置索引
    :type rhsInPinIndex: int
    :return: 如果连接成功，返回 True；否则返回 False
    """
    # 如果左侧节点为空，返回 False
    if lhsNode is None:
        return False

    # 如果右侧节点为空，返回 False
    if rhsNode is None:
        return False

    # 如果左侧节点的输出引脚索引不在有序输出引脚列表中，返回 False
    if lhsOutPinIndex not in lhsNode.orderedOutputs:
        return False

    # 如果右侧节点的输入引脚索引不在有序输入引脚列表中，返回 False
    if rhsInPinIndex not in rhsNode.orderedInputs:
        return False

    # 获取左侧节点的输出引脚
    lhsPin = lhsNode.orderedOutputs[lhsOutPinIndex]
    # 获取右侧节点的输入引脚
    rhsPin = rhsNode.orderedInputs[rhsInPinIndex]

    # 调用 connectPins 函数连接引脚
    return connectPins(lhsPin, rhsPin)


def traverseConstrainedPins(startFrom, callback):
    """
    遍历受约束和连接的引脚。
    遍历所有类型为 :class:`Any <PyFlow.Packages.PyFlowBase.Pins.AnyPin.AnyPin>` 的受约束链式引脚，并将引脚传递给回调函数。
    回调函数将为每个引脚执行一次。

    :param startFrom: 开始迭代的第一个引脚
    :type startFrom: :class:`~PyFlow.Core.PinBase.PinBase`
    :param callback: 每个迭代引脚要执行的函数。
    :type callback: callback(:class:`~PyFlow.Core.PinBase.PinBase`)
    """
    # 如果起始引脚不是任意类型，直接返回
    if not startFrom.isAny():
        return
    # 用于记录已经遍历过的引脚
    traversed = set()

    def worker(pin):
        """
        递归遍历受约束和连接的引脚。

        :param pin: 当前要处理的引脚
        :type pin: :class:`~PyFlow.Core.PinBase.PinBase`
        """
        # 将当前引脚标记为已遍历
        traversed.add(pin)
        # 调用回调函数处理当前引脚
        callback(pin)

        # 如果当前引脚没有约束，节点引脚集合为空
        if pin.constraint is None:
            nodePins = set()
        else:
            # 否则，获取当前引脚所属节点的约束引脚集合
            nodePins = set(pin.owningNode().constraints[pin.constraint])

        # 遍历与当前引脚连接的所有引脚
        for connectedPin in getConnectedPins(pin):
            # 如果连接的引脚是任意类型，将其添加到节点引脚集合中
            if connectedPin.isAny():
                nodePins.add(connectedPin)
        # 遍历节点引脚集合中的所有邻居引脚
        for neighbor in nodePins:
            # 如果邻居引脚未被遍历过，递归调用 worker 函数处理该引脚
            if neighbor not in traversed:
                worker(neighbor)

    # 调用 worker 函数开始遍历
    worker(startFrom)


def disconnectPins(src, dst):
    """
    断开两个引脚的连接。

    :param src: 左侧引脚
    :type src: :py:class:`~PyFlow.Core.PinBase.PinBase`
    :param dst: 右侧引脚
    :type dst: :py:class:`PyFlow.Core.PinBase.PinBase`
    :returns: 如果断开连接成功，返回 True；否则返回 False
    :rtype: bool
    """
    # 如果两个引脚已经连接
    if arePinsConnected(src, dst):
        # 如果源引脚是输入引脚，交换源引脚和目标引脚
        if src.direction == PinDirection.Input:
            src, dst = dst, src
        # 从源引脚的影响列表中移除目标引脚
        src.affects.remove(dst)
        # 从目标引脚的被影响列表中移除源引脚
        dst.affected_by.remove(src)
        # 通知源引脚已断开与目标引脚的连接
        src.pinDisconnected(dst)
        # 通知目标引脚已断开与源引脚的连接
        dst.pinDisconnected(src)
        # 调用 push 函数，标记相关引脚为脏数据
        push(dst)
        # 如果源引脚和目标引脚都是执行引脚，断开源引脚的执行信号与目标引脚的调用方法的连接
        if src.isExec() and dst.isExec():
            src.onExecute.disconnect(dst.call)
        return True
    return False


def push(start_from):
    """
    从起始引脚开始，将右侧所有端口标记为脏数据。
    这部分图将在每个时钟周期重新计算。

    :param start_from: 递归开始的引脚
    :type start_from: :py:class:`~PyFlow.Core.PinBase.PinBase`
    """
    #print("push", start_from.name, start_from.owningNode().name)
    # 如果起始引脚有影响的引脚
    if not len(start_from.affects) == 0:
        # 标记起始引脚为脏数据
        start_from.setDirty()
        # 遍历起始引脚的所有影响引脚
        for i in start_from.affects:
            # 标记影响引脚为脏数据
            i.setDirty()
            # 递归调用 push 函数，继续标记右侧的引脚为脏数据
            push(i)


def extractDigitsFromEndOfString(string):
    """
    获取字符串末尾的数字。

    示例:

    >>> nums = extractDigitsFromEndOfString("h3ello154")
    >>> print(nums, type(nums))
    >>> 154 <class 'int'>

    :param string: 输入的带数字的字符串
    :type string: str
    :returns: 字符串末尾的数字
    :rtype: int
    """
    # 使用正则表达式查找字符串末尾的数字
    result = re.search(r"(\d+)$", string)
    if result is not None:
        # 如果找到数字，将其转换为整数并返回
        return int(result.group(0))


def removeDigitsFromEndOfString(string):
    """
    删除字符串末尾的数字。

    类似于 :func:`~PyFlow.Core.Common.extractDigitsFromEndOfString`，但删除末尾的数字。

    :param string: 输入的字符串
    :type string: string
    :returns: 修改后的字符串
    :rtype: string
    """
    # 使用正则表达式替换字符串末尾的数字为空字符串
    return re.sub(r"\d+$", "", string)


def getUniqNameFromList(existingNames, name):
    """
    创建唯一的名称。
    遍历 **existingNames** 并提取末尾的数字，以找到一个新的唯一 ID。

    :param existingNames: 要搜索现有索引的字符串列表或集合
    :type existingNames: list[str]|set[str]
    :param name: 要获取唯一版本的名称
    :type name: str
    :returns: 与 existingNames 中任何名称都不重叠的新名称
    :rtype: str
    """
    # 如果名称不在现有名称列表中，直接返回该名称
    if name not in existingNames:
        return name
    # 用于存储现有名称中的数字
    ids = set()
    # 遍历现有名称列表
    for existingName in existingNames:
        # 提取现有名称末尾的数字
        digits = extractDigitsFromEndOfString(existingName)
        if digits is not None:
            # 如果提取到数字，将其添加到 ids 集合中
            ids.add(digits)
    # 调用 findGoodId 函数找到一个唯一的 ID
    idx = findGoodId(ids)
    # 移除名称末尾的数字
    nameNoDigits = removeDigitsFromEndOfString(name)
    # 返回添加唯一 ID 后的新名称
    return nameNoDigits + str(idx)


def clearSignal(signal):
    """
    断开信号的所有接收器。

    :param signal: 信号发射器
    :type signal: :class:`~blinker.base.Signal`
    """
    # 遍历信号的所有接收器
    for receiver in list(signal.receivers.values()):
        if isinstance(receiver, weakref.ref):
            # 如果接收器是弱引用，断开该弱引用对应的对象
            signal.disconnect(receiver())
        else:
            # 否则，直接断开该接收器
            signal.disconnect(receiver)


class SingletonDecorator:
    """
    装饰器，用于使类成为单例类，每次调用都返回相同的对象。
    """
    # 存储所有单例实例的列表
    allInstances = []

    @staticmethod
    def destroyAll():
        """
        销毁所有单例实例。
        """
        for instance in SingletonDecorator.allInstances:
            instance.destroy()

    def __init__(self, cls):
        """
        初始化单例装饰器。

        :param cls: 要装饰的类
        """
        # 保存要装饰的类
        self.cls = cls
        # 初始化单例实例为 None
        self.instance = None
        # 将当前装饰器实例添加到 allInstances 列表中
        self.allInstances.append(self)

    def destroy(self):
        """
        销毁单例实例。
        """
        if ('destroy' in dir(self.instance)):
            # 如果实例有 destroy 方法，调用该方法
            self.instance.destroy()
        # 删除实例引用
        del self.instance
        # 将实例引用设置为 None
        self.instance = None

    def __call__(self, *args, **kwds):
        """
        当装饰器被调用时，返回单例实例。

        :param args: 位置参数
        :param kwds: 关键字参数
        :return: 单例实例
        """
        if self.instance is None:
            # 如果实例不存在，创建一个新的实例
            self.instance = self.cls(*args, **kwds)
        if hasattr(self.instance, 'instanceCount'):
            # 如果实例有 instanceCount 属性，将其加 1
            self.instance.instanceCount += 1
        return self.instance


class DictElement(tuple):
    """
    PyFlow 字典元素类。
    这个 Python :class:`tuple` 的子类用于表示字典元素，以构建类型化的字典。
    """

    def __new__(cls, a=None, b=None):
        """
        创建一个新的 DictElement 实例。

        :param a: 第一个元素，默认为 None
        :param b: 第二个元素，默认为 None
        :return: 新的 DictElement 实例
        """
        if a is None and b is None:
            # 如果两个参数都为 None，创建一个空元组
            new = ()
        elif b is None:
            if isinstance(a, tuple) and len(a) <= 2:
                # 如果 a 是长度不超过 2 的元组，直接使用 a
                new = a
            else:
                # 否则，抛出异常
                raise Exception("Invalid Input")
        else:
            # 否则，创建一个包含 a 和 b 的元组
            new = (a, b)
        return super(DictElement, cls).__new__(cls, new)


class PFDict(dict):
    """
    这个 Python :class:`dict` 的子类实现了一个键类型化的字典。
    只有定义的数据类型可以用作键，并且只有可哈希的类型才能使用，由以下代码确定：

    >>> isinstance(dataType, collections.abc.Hashable)

    要使一个类可哈希，需要实现一些方法：

    示例:
    ::

        class C:
            def __init__(self, x):
                self.x = x
            def __repr__(self):
                return "C({})".format(self.x)
            def __hash__(self):
                return hash(self.x)
            def __eq__(self, other):
                return (self.__class__ == other.__class__ and self.x == other.x)
    """

    def __init__(self, keyType, valueType="AnyPin", inp=None):
        """
        初始化 PFDict 实例。

        :param keyType: 键的数据类型
        :param valueType: 值的数据类型，默认为 "AnyPin"
        :type valueType: 可选
        :param inp: 从另一个字典构建，默认为 {}
        :type inp: dict, 可选
        """
        if inp is None:
            inp = {}
        # 调用父类的构造函数
        super(PFDict, self).__init__(inp)
        # 保存键的数据类型
        self.keyType = keyType
        # 保存值的数据类型
        self.valueType = valueType

    def __setitem__(self, key, item):
        """
        重新实现 Python 字典的 __setitem__ 方法，只允许类型化的键。
        如果键的类型无效，将抛出异常。

        :param key: 键
        :param item: 值
        """
        if type(key) == self.getClassFromType(self.keyType):
            # 如果键的类型与定义的键类型匹配，调用父类的 __setitem__ 方法
            super(PFDict, self).__setitem__(key, item)
        else:
            # 否则，抛出异常
            raise Exception(
                "Valid key should be a {0}".format(self.getClassFromType(self.keyType))
            )

    @staticmethod
    def getClassFromType(pinType):
        """
        获取定义的引脚类型的内部数据结构。

        :param pinType: 引脚类型名称
        :type pinType: class 或 None
        :return: 引脚类型的内部数据结构类
        """
        # 根据引脚类型查找引脚类
        pin = findPinClassByType(pinType)
        if pin:
            # 如果找到引脚类，返回其内部数据结构类
            pinClass = pin.internalDataStructure()
            return pinClass
        return None


class PinReconnectionPolicy(IntEnum):
    """
    当引脚已经有连接，并且即将进行另一个连接时的行为策略。
    """

    DisconnectIfHasConnections = 0  #: 当前连接将被断开
    ForbidConnection = 1  #: 新连接将被取消


class PinOptions(Flag):
    """
    用于确定引脚的行为。
    在引脚实例上应用标志。

    .. seealso:: :meth:`~PyFlow.Core.PinBase.PinBase.enableOptions` :meth:`~PyFlow.Core.PinBase.PinBase.disableOptions`
    """

    ArraySupported = auto()  #: 引脚可以持有数组数据结构
    DictSupported = auto()  #: 引脚可以持有字典数据结构
    SupportsOnlyArrays = (
        auto()
    )  #: 引脚将只支持其他具有数组数据结构的引脚

    AllowMultipleConnections = (
        auto()
    )  #: 此标志启用引脚允许多个输入连接。请参阅 :func:`~PyFlow.Core.Common.connectPins`

    ChangeTypeOnConnection = (
        auto()
    )  #: 由 :class:`~PyFlow.Packages.PyFlowBase.Pins.AnyPin.AnyPin` 使用，用于确定在新连接时是否可以更改其数据类型。
    RenamingEnabled = auto()  #: 确定引脚是否可以重命名
    Dynamic = (
        auto()
    )  #: 指定引脚是否在程序运行时动态创建
    AlwaysPushDirty = auto()  #: 引脚将始终被视为脏数据（需要重新计算）
    Storable = auto()  #: 确定引脚数据在序列化时是否可以存储
    AllowAny = (
        auto()
    )  #: 特殊标志，允许引脚成为 :class:`~PyFlow.Packages.PyFlowBase.Pins.AnyPin.AnyPin`，这意味着无类型且不会被标记为错误。默认情况下，:py:class:`PyFlow.Packages.PyFlowBase.Pins.AnyPin.AnyPin` 需要用某种数据类型（其他定义的引脚）初始化。此标志覆盖该规则。用于列表和无类型节点
    DictElementSupported = (
        auto()
    )  #: 字典由 :class:`DictElement` 对象构建。因此，字典引脚将只允许其他字典，直到此标志启用。用于 :class:`~PyFlow.Packages.PyFlowBase.Nodes.makeDict` 节点


class StructureType(IntEnum):
    """
    用于确定值的结构类型。
    """

    Single = 0  #: 单个数据结构
    Array = (
        1
    )  #: Python 列表结构，以数组（类型化）和列表（非类型化）表示
    Dict = 2  #: :py:class:`PFDict` 结构，基本上是一个键类型化的 Python 字典
    Multi = (
        3
    )  #: 这意味着它可以在连接或用户操作时变为上述任何一种结构


def findStructFromValue(value):
    """
    从值中查找 :class:`~PyFlow.Core.Common.StructureType`。

    :param value: 用于查找结构的输入值。
    :returns: 输入值的结构类型
    :rtype: :class:`~PyFlow.Core.Common.StructureType`
    """

    if isinstance(value, list):
        # 如果值是列表，返回数组结构类型
        return StructureType.Array
    if isinstance(value, dict):
        # 如果值是字典，返回字典结构类型
        return StructureType.Dict
    # 否则，返回单个数据结构类型
    return StructureType.Single


class PinSelectionGroup(IntEnum):
    """
    用于 :meth:`~PyFlow.Core.NodeBase.NodeBase.getPinSG` 以进行优化。
    """

    Inputs = 0  #: 输入引脚
    Outputs = 1  #: 输出引脚
    BothSides = 2  #: 两侧引脚


class AccessLevel(IntEnum):
    """
    可用于代码生成。
    """

    public = 0  #: 公共
    private = 1  #: 私有
    protected = 2  #: 受保护


class PinDirection(IntEnum):
    """
    确定引脚是输入引脚还是输出引脚。
    """

    Input = 0  #: 左侧引脚
    Output = 1  #: 右侧引脚


class NodeTypes(IntEnum):
    """
    确定节点是可调用节点还是纯节点。
    """

    Callable = 0  #: 可调用节点是具有执行引脚的节点
    Pure = 1  #: 普通节点


class Direction(IntEnum):
    """
    方向标识符。
    """

    Left = 0  #: 左
    Right = 1  #: 右
    Up = 2  #: 上
    Down = 3  #: 下


class PinSpecifiers:
    """
    引脚说明符常量。

    :var SUPPORTED_DATA_TYPES: 用于指定支持的数据类型列表
    :var CONSTRAINT: 用于指定类型约束键
    :var STRUCT_CONSTRAINT: 用于指定结构约束键
    :var ENABLED_OPTIONS: 用于启用选项
    :var DISABLED_OPTIONS: 用于禁用选项
    :var INPUT_WIDGET_VARIANT: 用于指定小部件变体字符串
    :var DESCRIPTION: 用于指定引脚的描述，将用作工具提示
    :var VALUE_LIST: 特定于字符串引脚。如果指定，将创建组合框
    :var VALUE_RANGE: 特定于整数和浮点数。如果指定，将创建滑块而不是值框
    :var DRAGGER_STEPS: 用于指定自定义值拖动器步骤
    """

    SUPPORTED_DATA_TYPES = "supportedDataTypes"
    CONSTRAINT = "constraint"
    STRUCT_CONSTRAINT = "structConstraint"
    ENABLED_OPTIONS = "enabledOptions"
    DISABLED_OPTIONS = "disabledOptions"
    INPUT_WIDGET_VARIANT = "inputWidgetVariant"
    DESCRIPTION = "Description"
    VALUE_LIST = "ValueList"
    VALUE_RANGE = "ValueRange"
    DRAGGER_STEPS = "DraggerSteps"


class NodeMeta:
    """
    节点元数据常量。

    :var CATEGORY: 用于指定节点的类别。节点框将考虑此类别
    :var KEYWORDS: 用于指定附加关键字列表，用于节点框搜索字段
    :var CACHE_ENABLED: 用于指定节点是否缓存
    """

    CATEGORY = "Category"
    KEYWORDS = "Keywords"
    CACHE_ENABLED = "CacheEnabled"